#include "graph.h"
#include <QMenu>
#include <QActionGroup>
#include <QKeyEvent>
#include <QPainter>
#include <QFile>


const static int s_scale = 2;
const static qreal s_default_unit = 0.1;
const static int s_default_space = 40;
const static int s_max_space = 80;
const static int s_min_space = 20;
const static int s_icon_size = 8;

static int s_curve_id = 0;

Graph::Graph(QWidget *parent)
    : QWidget{parent}
{
    //this->setAttribute(Qt::WA_StyledBackground, true);
    this->setFocusPolicy(Qt::StrongFocus);

    m_unitSpace = s_default_space;
    m_unit = s_default_unit;

    QFile file(":/qss/style.qss");
    file.open(QFile::ReadOnly);
    if(file.isOpen()){
        QString qss = file.readAll();
        this->setStyleSheet(qss);
    }
    file.close();

    m_pMenu = new QMenu(this);

    m_actAddKey = new QAction("Add Key", this);
    m_actDeleteKey = new QAction("Delete Key", this);

    connect(m_actAddKey, &QAction::triggered, this, &Graph::onAddKey);
    connect(m_actDeleteKey, &QAction::triggered, this, &Graph::onDeleteKey);

    m_grpTangentMode = new QActionGroup(this);
    m_grpTangentMode->setExclusive(true);

    connect(m_grpTangentMode, &QActionGroup::triggered, this, &Graph::onTangentModeSelected);

#define XX(name) \
    m_act##name = new QAction(#name, this); \
        m_act##name->setCheckable(true); \
        m_grpTangentMode->addAction(m_act##name);

    LIST_ALL_TANGENT_MODE
#undef XX

}

void Graph::moveTo(qreal x, qreal y)
{
    if (qFuzzyCompare(m_start.x(), x) && qFuzzyCompare(m_start.y(), y))
        return;
    m_start.setX(x);
    m_start.setY(y);

    repaint();

    emit startPositionChanged(m_start);

}

int Graph::unitSpace() const
{
    return m_unitSpace;
}

qreal Graph::unit() const
{
    return m_unit;
}

QPointF Graph::start() const
{
    return m_start;
}

QPoint Graph::transformBack(const QPointF &pt) const
{
    QPointF cur = pt - m_start;
    qreal scale = m_unitSpace / m_unit;
    return QPoint(scale * cur.x(), height() - scale * cur.y());
}

QPointF Graph::transformTo(const QPoint &pt) const
{
    qreal scale = m_unit / m_unitSpace;
    return QPointF(scale * pt.x(), scale * (height() - pt.y())) + m_start;
}

void Graph::paintGrid()
{
    qreal scale = m_unitSpace / m_unit;
    //预留显示数字的空间
    int width = this->width();
    int height = this->height();

    int count = width / m_unitSpace;
    qreal num = qCeil(m_start.x() / m_unit) * m_unit; //标尺开始的数字
    int pos = (num - m_start.x()) * scale;

    QPainter painter(this);

    //每个单位距离
    for(int i = 0; i <= count; ++ i){


        QPen pen = painter.pen();
        if(qRound(num / m_unit) % 10 == 0){
            pen.setColor(QColor(55, 55, 55));
            pen.setStyle(Qt::PenStyle::SolidLine);
            pen.setWidth(2);
            painter.setPen(pen);

        }else{
            pen.setColor(QColor(22, 22, 22));
            pen.setWidth(1);
            pen.setStyle(Qt::PenStyle::DashLine);
            painter.setPen(pen);
        }

        QPoint pt1(pos, 0);
        QPoint pt2(pos, height);
        painter.drawLine(pt1, pt2);

        num = QString::asprintf("%.3f", num + m_unit).toDouble();
        pos += m_unitSpace;
    }

    count = height / m_unitSpace;
    num = qCeil(m_start.y() / m_unit) * m_unit; //标尺开始的数字
    pos = height - (num - m_start.y()) * scale;

    //每个单位距离
    for(int i = 0; i <= count; ++ i){

        QPen pen = painter.pen();
        if(qRound(num / m_unit) % 10 == 0){
            pen.setColor(QColor(55, 55, 55));
            pen.setStyle(Qt::PenStyle::SolidLine);
            pen.setWidth(2);
            painter.setPen(pen);

        }else{
            pen.setColor(QColor(22, 22, 22));
            pen.setWidth(1);
            pen.setStyle(Qt::PenStyle::DashLine);
            painter.setPen(pen);
        }

        QPoint pt1(0, pos);
        QPoint pt2(width, pos);
        painter.drawLine(pt1, pt2);

        num = QString::asprintf("%.3f", num + m_unit).toDouble();
        pos -= m_unitSpace;
    }

    QRect rect(0, 0, width, height);
    QPen pen = painter.pen();
    pen.setStyle(Qt::PenStyle::SolidLine);
    pen.setColor(QColor(200, 55, 55));
    pen.setWidth(3);
    painter.setPen(pen);
    painter.drawRect(rect);
}

void Graph::paintCurves()
{
    for(const auto& id:m_activeCurve){
        if(m_curves.count(id) == 0)
            continue;
        auto curve = m_curves[id];
        paintCurve(curve);
        if(id != m_editingCurveId)
            continue;
        curve->visitAllPoints([&](const SplinePoint::ptr& pt){
            if(isInViewRect(pt->getPoint())){
                paintKeyPoint(pt);
            }
        });
    }
}

void Graph::paintSelectRect()
{
    if(m_key != Qt::Key_Shift || !(m_mouseBtn & Qt::LeftButton))
        return;

    if(m_mousePosition == m_mousePressPosition)
        return;


    QPainter paint(this);
    paint.setPen(Qt::PenStyle::DashDotLine);
    paint.drawRect(m_selectRect);
}

void Graph::paintKeyPoint(const SplinePoint::ptr &sp)
{

    QPainter painter(this);


    QPoint realPoint = transformBack(sp->getPoint());


    QBrush brush = painter.brush();
    brush.setStyle(Qt::BrushStyle::SolidPattern);


    switch (sp->getTangentMode()) {
    case TangentMode::Linear:
    case TangentMode::Constant:
    case TangentMode::Weighted:{

        if(sp->isSelect()){
            brush.setColor(QColor(235, 224, 106));
        }else{
            brush.setColor(QColor(211, 211, 211));
        }
        painter.setBrush(brush);
        static const QPoint triangel[3] = {QPoint(-s_icon_size / 2, s_icon_size / 2), QPoint(0, -s_icon_size), QPoint(s_icon_size / 2, s_icon_size / 2)};
        painter.translate(realPoint.x(), realPoint.y());
        painter.drawPolygon(triangel, 3);

        break;
    }
    case TangentMode::Auto:
    case TangentMode::User:
    case TangentMode::Break:{

        if(sp->isSelect()){
            painter.save();
            painter.setRenderHint(QPainter::Antialiasing);
            QPoint realHandle1 = transformBack(sp->getHandle1());
            QPoint realHandle2 = transformBack(sp->getHandle2());

            QPen pen = painter.pen();
            pen.setWidth(2);
            pen.setColor(QColor(211, 145, 45));
            painter.setPen(pen);
            painter.drawLine(realHandle1, realPoint);
            painter.drawLine(realHandle2, realPoint);

            painter.restore();

            if(sp->getTangentMode() == TangentMode::Break){
                if(sp->getSelect() == SplinePoint::_Handle1){
                    brush.setColor(QColor(235, 224, 106));
                }else{
                    brush.setColor(QColor(211, 211, 211));
                }
                painter.setBrush(brush);
                painter.drawEllipse(realHandle1, s_icon_size / 2, s_icon_size / 2);
                if(sp->getSelect() == SplinePoint::_Handle2){
                    brush.setColor(QColor(235, 224, 106));
                }else{
                    brush.setColor(QColor(211, 211, 211));
                }
                painter.setBrush(brush);
                painter.drawEllipse(realHandle2, s_icon_size / 2, s_icon_size / 2);
            }else{
                if(sp->getSelect() == SplinePoint::_Handle1 || sp->getSelect() == SplinePoint::_Handle2){
                    brush.setColor(QColor(235, 224, 106));
                }else{
                    brush.setColor(QColor(211, 211, 211));
                }
                painter.setBrush(brush);
                painter.drawEllipse(realHandle1, s_icon_size / 2, s_icon_size / 2);
                painter.drawEllipse(realHandle2, s_icon_size / 2, s_icon_size / 2);
            }
        }


        if(sp->getSelect() == SplinePoint::_Point){
            brush.setColor(QColor(235, 224, 106));
        }else{
            brush.setColor(QColor(211, 211, 211));
        }
        painter.setBrush(brush);
        painter.translate(realPoint.x(), realPoint.y());
        painter.rotate(45);
        painter.translate(-s_icon_size / 2, -s_icon_size / 2);

        painter.drawRect(0, 0, s_icon_size, s_icon_size);

        break;
    }
    default:
        break;
    }
}

void Graph::paintCurve(const SplineCurve::ptr &curve)
{

    QPointF end = transformTo(rect().bottomRight());

    std::vector<QPointF> arr = curve->getCurve(m_start.x(), end.x());
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    QPen pen = painter.pen();
    pen.setColor(curve->color());
    pen.setWidth(2);
    painter.setPen(pen);
    QPoint lastPoint;
    int count = 0;
    for(const auto& pt:arr){
        QPoint point = transformBack(pt);
        if(count > 0){
            painter.drawLine(lastPoint, point);
        }
        count += 1;
        lastPoint = point;
    }
}

bool Graph::isInViewRect(const QPointF &pt) const
{
    QPointF end = transformTo(rect().topRight());
    QRectF viewRect(m_start, end);
    return viewRect.contains(pt);
}

void Graph::selectPoints()
{
    if(m_editingCurveId.isNull() || m_editingCurveId.isEmpty())
        return;
    if(m_activeCurve.empty())
        return;
    if(m_activeCurve.count(m_editingCurveId) == 0)
        return;
    if(m_curves.count(m_editingCurveId) == 0)
        return;


    QRectF realRect(transformTo(m_selectRect.topLeft()), transformTo(m_selectRect.bottomRight()));

    bool isAddSelect = m_key == Qt::Key_Shift;
    bool hasSelect = false;
    auto curve = m_curves[m_editingCurveId];
    curve->selectPoints(realRect, isAddSelect);

    curve->visitSelectedPoints([&](const QPointF& pt){
        emit selectPoint(pt);
        hasSelect = true;
    });
    emit hasSelectedPoint(hasSelect);
    update();
}

void Graph::movePoints(QPointF point)
{
    if(m_editingCurveId.isNull() || m_editingCurveId.isEmpty())
        return;
    if(m_activeCurve.empty())
        return;
    if(m_activeCurve.count(m_editingCurveId) == 0)
        return;
    if(m_curves.count(m_editingCurveId) == 0)
        return;

    auto curve = m_curves[m_editingCurveId];
    curve->moveSelectPoints(point);

    emit movePoint(point);
    update();
}

QString Graph::curveName(const QString &id) const
{
    auto it = m_curves.find(id);
    if(it != m_curves.end())
        return it->second->name();
    return "";
}

QColor Graph::curveColor(const QString &id) const
{
    auto it = m_curves.find(id);
    if(it != m_curves.end())
        return it->second->color();
    return Qt::white;
}

void Graph::setEditingCurve(const QString &id)
{
    m_editingCurveId = id;
    update();
}

void Graph::changedPosition(QPointF position)
{
    if(m_editingCurveId.isNull() || m_editingCurveId.isEmpty())
        return;
    if(m_activeCurve.empty())
        return;
    if(m_activeCurve.count(m_editingCurveId) == 0)
        return;
    if(m_curves.count(m_editingCurveId) == 0)
        return;

    auto curve = m_curves[m_editingCurveId];
    curve->setSelectPointsPosition(position);
    repaint();
}

void Graph::onAddKey()
{
    if(m_editingCurveId.isNull() || m_editingCurveId.isEmpty())
        return;
    if(m_activeCurve.empty())
        return;
    if(m_activeCurve.count(m_editingCurveId) == 0)
        return;
    if(m_curves.count(m_editingCurveId) == 0)
        return;

    auto curve = m_curves[m_editingCurveId];

    QPointF pt = transformTo(m_mousePressPosition);
    curve->addPoint(pt, SplinePoint::Select::_Point);
    update();
}

void Graph::onDeleteKey()
{
    if(m_editingCurveId.isNull() || m_editingCurveId.isEmpty())
        return;
    if(m_activeCurve.empty())
        return;
    if(m_activeCurve.count(m_editingCurveId) == 0)
        return;
    if(m_curves.count(m_editingCurveId) == 0)
        return;

    auto curve = m_curves[m_editingCurveId];
    curve->deletePoint();
    update();
}

void Graph::onTangentModeSelected(QAction *action)
{
#define XX(name) \
    if(m_act##name == action) {\
            /*qDebug() << #name;*/ \
        if(m_editingCurveId.isNull() || m_editingCurveId.isEmpty())\
            return;\
        if(m_activeCurve.empty())\
            return;\
        if(m_activeCurve.count(m_editingCurveId) == 0)\
            return;\
        if(m_curves.count(m_editingCurveId) == 0)\
            return;\
        auto curve = m_curves[m_editingCurveId];\
        curve->setTangent(TangentMode::name);\
        update(); \
    }
    LIST_ALL_TANGENT_MODE
#undef XX
}


QString Graph::addCurve(const QString& name)
{
    QString id = QString::number(++s_curve_id);
    m_curves[id] = std::make_shared<SplineCurve>(name);
    m_activeCurve.emplace(id);
    repaint();
    return id;
}

void Graph::deleteCurve(const QString& id)
{
    if(id.isEmpty()){
        m_activeCurve.clear();
        m_curves.clear();
    }else{
        m_activeCurve.erase(id);
        m_curves.erase(id);
    }
    repaint();

}

void Graph::clearSelect(const QString& id)
{
    if(id.isEmpty()){
        m_activeCurve.clear();
    }else{
        m_activeCurve.erase(id);

    }
    repaint();

}

void Graph::selectCurve(const QString& id)
{
    m_activeCurve.emplace(id);
    repaint();
}

bool Graph::hasSelectPoint()
{

    if(m_editingCurveId.isNull() || m_editingCurveId.isEmpty())
        return false;
    if(m_activeCurve.empty())
        return false;
    if(m_activeCurve.count(m_editingCurveId) == 0)
        return false;
    if(m_curves.count(m_editingCurveId) == 0)
        return false;

    auto curve = m_curves[m_editingCurveId];
    bool res = curve->countSelectPoint() > 0;

    emit hasSelectedPoint(res);
    return res;
}

void Graph::paintEvent(QPaintEvent *event)
{
    //绘制表格
    paintGrid();
    //绘制选择框
    paintSelectRect();
    //绘制曲线
    paintCurves();
}

void Graph::mousePressEvent(QMouseEvent *event)
{
    m_mouseBtn |= event->button();
    m_mousePosition = event->pos();
    m_mousePressPosition = m_mousePosition;

    if(m_mouseBtn & Qt::LeftButton){
        QPoint leftTop(m_mousePosition.x() - s_icon_size / 2, m_mousePosition.y() - s_icon_size / 2);
        QPoint rightBottom(m_mousePosition.x() + s_icon_size / 2, m_mousePosition.y() + s_icon_size / 2);
        m_selectRect.setTopLeft(leftTop);
        m_selectRect.setBottomRight(rightBottom);
        selectPoints();
    }


    QWidget::mousePressEvent(event);
}

void Graph::mouseReleaseEvent(QMouseEvent *event)
{
    m_mouseBtn &= ~event->button();
    update();
    QWidget::mouseReleaseEvent(event);
}

void Graph::mouseMoveEvent(QMouseEvent *event)
{

    QPoint lastPosition = m_mousePosition;
    m_mousePosition = event->pos();
    if(m_mouseBtn & Qt::LeftButton){
        if(m_key == Qt::Key_Shift){
            //框选模式
            QPoint leftTop(qMin(m_mousePressPosition.x(), m_mousePosition.x()), qMin(m_mousePressPosition.y(), m_mousePosition.y()));
            QPoint rightBottom(qMax(m_mousePressPosition.x(), m_mousePosition.x()), qMax(m_mousePressPosition.y(), m_mousePosition.y()));
            m_selectRect.setTopLeft(leftTop);
            m_selectRect.setBottomRight(rightBottom);
            selectPoints();
        }else if(hasSelectPoint()){
            qreal scale = m_unit / m_unitSpace;
            QPointF offset = (m_mousePosition - lastPosition).toPointF() * scale;
            offset.setY(-offset.y());
            movePoints(offset);
        }else{
            qreal scale = m_unit / m_unitSpace;

            QPointF offset = (m_mousePosition - lastPosition).toPointF() * scale;
            moveTo(m_start.x() - offset.x(), m_start.y() + offset.y());
        }

    }else if(m_mouseBtn & Qt::MiddleButton){
        qreal scale = m_unit / m_unitSpace;
        QPointF offset = (m_mousePosition - lastPosition).toPointF() * scale;
        offset.setY(-offset.y());
        movePoints(offset);
    }

    QWidget::mouseMoveEvent(event);
}

void Graph::wheelEvent(QWheelEvent *event)
{
    m_mousePosition = this->mapFromGlobal(cursor().pos());
    QPointF before = transformTo(m_mousePosition);
    if(event->angleDelta().y() > 0){
        m_unitSpace += s_scale;
    }else{
        m_unitSpace -= s_scale;
    }

    if(m_unitSpace >= s_max_space){
        m_unitSpace = s_default_space;
        m_unit /= s_scale;
    }else if(m_unitSpace <= s_min_space){
        m_unitSpace = s_default_space;
        m_unit *= s_scale;
    }

    QPointF after = transformTo(m_mousePosition);
    moveTo(m_start.x() + before.x() - after.x(), m_start.y() + before.y() - after.y());

    emit zoomChanged(m_unitSpace, m_unit);

    QWidget::wheelEvent(event);
}

void Graph::keyPressEvent(QKeyEvent *event)
{
    m_key = event->key();
    qreal offset = 0.01;
    switch (m_key) {
    case Qt::Key_A:
    case Qt::Key_Left:
        moveTo(m_start.x() + offset, m_start.y());
        break;
    case Qt::Key_D:
    case Qt::Key_Right:
        moveTo(m_start.x() - offset, m_start.y());
        break;
    case Qt::Key_W:
    case Qt::Key_Up:
        moveTo(m_start.x(), m_start.y() - offset);
        break;
    case Qt::Key_S:
    case Qt::Key_Down:
        moveTo(m_start.x(), m_start.y() + offset);
        break;
    default:
        break;
    }

    QWidget::keyPressEvent(event);
}

void Graph::keyReleaseEvent(QKeyEvent *event)
{
    m_key = 0;
    QWidget::keyReleaseEvent(event);

}

void Graph::contextMenuEvent(QContextMenuEvent *event)
{
    Q_UNUSED(event);
    if(m_editingCurveId.isNull() || m_editingCurveId.isEmpty())
        return;
    if(m_activeCurve.empty())
        return;
    if(m_activeCurve.count(m_editingCurveId) == 0)
        return;
    if(m_curves.count(m_editingCurveId) == 0)
        return;


    m_mousePressPosition = this->mapFromGlobal(cursor().pos());

    m_pMenu->clear();

    m_pMenu->addAction(m_actAddKey);
    m_pMenu->addAction(m_actDeleteKey);

    if(hasSelectPoint()){
        m_pMenu->addSeparator();
#define XX(name) \
        {\
                m_pMenu->addAction(m_act##name);\
                auto curve = m_curves[m_editingCurveId]; \
                bool isChecked = curve->checkSelectPointTangentMode(TangentMode::name); \
                m_act##name->setChecked(isChecked);\
        }

        LIST_ALL_TANGENT_MODE
#undef XX
            m_pMenu->addSeparator();
    }

    m_pMenu->exec(QCursor::pos());
}
